package com.mezz.bones.im.gateway.service.impl;

import cn.hutool.core.date.DateUtil;
import com.mezz.bones.im.common.enums.OnlineStatusEnum;
import com.mezz.bones.im.common.domain.user.ImUserClientInfo;
import com.mezz.bones.im.common.domain.user.ImUserConnectInfo;
import com.mezz.bones.im.gateway.service.IUserStatusService;
import io.netty.channel.ChannelHandlerContext;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RKeys;
import org.redisson.api.RMap;
import org.redisson.api.RedissonClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.Duration;
import java.util.*;

/**
 * @Author: zhuang
 * @Date: 2024/12/9 10:55
 * @Description: 用户在线状态服务实现  可简单用KV实现存储
 * @Version: 1.0
 *
 *
 *
 * 1. 用户状态使用redis存储
 *      服务器重启用户连接对象丢失 redis状态没有更新 不一致
 *
 **/

@Service
@Slf4j
public class UserStatusServiceImpl implements IUserStatusService, CommandLineRunner {

    @Resource
    private RedissonClient redissonClient;

    private static final String ONLINE_STATUS_KEY_PREFIX = "im:user:info:"; // 用来标识用户在线状态的键前缀
    private static final long EXPIRE_TIME_DAY = 30; // 30天 暴露客户端登录信息

    /**
     * 用户在线方法，使用 map 存储，每个用户可以有多个连接（根据 clientType 和 imei）
     *
     * @param userInfo 用户信息对象
     * @param ip
     */
    @Override
    public void online(ImUserConnectInfo userInfo, ChannelHandlerContext ctx, String ip) {
        String mapKey = ONLINE_STATUS_KEY_PREFIX + userInfo.getUserId(); // 使用 uid 构建 Redis map 键
        RMap<String, ImUserClientInfo> statusMap = redissonClient.getMap(mapKey);

        // 构建具体的客户端类型和设备号键
        String clientKey = userInfo.getDeviceType() + "-" + userInfo.getImei();
        // 旧数据
        ImUserClientInfo oldClient = statusMap.get(clientKey);

        ImUserClientInfo imUserClientInfo = userConnectConvert2Client(userInfo, ctx, OnlineStatusEnum.ONLINE,ip);
        if(oldClient != null){
            //最后离线时间数据 使用旧数据的内容
            imUserClientInfo.setLastOfflineTime(oldClient.getLastOfflineTime());
        }

        // 设置该设备为在线状态，value为ImUserInfo对象
        statusMap.put(clientKey, imUserClientInfo);
        statusMap.expire(Duration.ofDays(EXPIRE_TIME_DAY));
    }

    @Override
    public void offline(ImUserConnectInfo userInfo) {

        String uid = userInfo.getUserId();
        String mapKey = ONLINE_STATUS_KEY_PREFIX + uid; // 使用 uid 构建 Redis map 键
        RMap<String, ImUserClientInfo> userClientInfoRMap = redissonClient.getMap(mapKey);

        if (userClientInfoRMap != null && !userClientInfoRMap.isEmpty()) {
            // 构建客户端类型和设备号的键
            String clientKey = userInfo.getDeviceType() + "-" + userInfo.getImei();

            // 获取设备状态信息
            ImUserClientInfo imUserClientInfo = userClientInfoRMap.get(clientKey);
            if (imUserClientInfo != null) {
                // 更新离线状态和最后离线时间
                imUserClientInfo.setOnline(OnlineStatusEnum.OFFLINE.getCode());
                imUserClientInfo.setLastOfflineTime(DateUtil.formatDateTime(new Date()));
                userClientInfoRMap.put(clientKey, imUserClientInfo);
                log.info("用户 [{}] 的设备 [{}] 已更新为离线状态", uid, clientKey);
            } else {
                log.warn("未找到用户 [{}] 的设备 [{}] 状态信息", uid, clientKey);
            }
        } else {
            log.warn("用户 [{}] 的在线状态映射为空或不存在", uid);
        }
    }


    /**
     * 把所有的设备都设置为离线状态
     * @param uid 用户 ID
     */
    @Override
    public void offline(String uid) {
        String mapKey = ONLINE_STATUS_KEY_PREFIX + uid; // 使用 uid 构建 Redis map 键
        RMap<String, ImUserClientInfo> statusMap = redissonClient.getMap(mapKey);

        // 检查 Map 是否为空
        if (statusMap == null || statusMap.isEmpty()) {
            log.warn("用户 [{}] 的在线状态映射为空，无需更新", uid);
            return;
        }

        // 获取当前时间
        Date now = new Date();
        String formattedTime = DateUtil.formatDateTime(now);

        // 遍历并更新设备状态
        statusMap.forEach((key, imUserClientInfo) -> {
            if (imUserClientInfo != null) {
                imUserClientInfo.setOnline(OnlineStatusEnum.OFFLINE.getCode());
                imUserClientInfo.setLastOfflineTime(formattedTime);
                statusMap.put(key, imUserClientInfo);

            }
        });

        log.info("用户 [{}] 的所有设备已更新为离线状态", uid);
    }


    /**
     * 查询用户是否在线，返回所有在线客户端信息
     * @param uid 用户ID
     * @return List<ImUserInfo> 用户所有在线连接信息
     */
    @Override
    public List<ImUserConnectInfo> allOnlineDevice(String uid) {
        String mapKey = ONLINE_STATUS_KEY_PREFIX + uid; // 使用 uid 构建 Redis map 键
        RMap<String, ImUserClientInfo> statusMap = redissonClient.getMap(mapKey);

        List<ImUserConnectInfo> onlineUsers = new ArrayList<>();

        // 如果 statusMap 为 null 或空，直接返回空列表
        if (statusMap == null || statusMap.isEmpty()) {
            return new ArrayList<>();
        }

        // 遍历用户所有连接，查找所有在线的连接
        for (Map.Entry<String, ImUserClientInfo> entry : statusMap.entrySet()) {
            if (entry.getValue() != null) {
                ImUserClientInfo clientInfo = entry.getValue();
                ImUserConnectInfo connectInfo = new ImUserConnectInfo();
                connectInfo.setUserId(clientInfo.getUserId());
                connectInfo.setDeviceType(clientInfo.getDeviceType());
                connectInfo.setImei(clientInfo.getImei());
                onlineUsers.add(connectInfo); // 添加在线连接的用户信息
            }
        }
        return onlineUsers; // 返回所有在线的连接信息
    }

    /**
     * 查询某个设备是否在线，按 ImUserInfo 判断具体设备的在线状态
     * @return boolean 是否在线
     */
    @Override
    public boolean isOnline(ImUserConnectInfo userInfo) {

        if(userInfo == null){
            return false;
        }

        String uid = userInfo.getUserId();
        String deviceType = userInfo.getDeviceType();
        String imei = userInfo.getImei();

        String mapKey = ONLINE_STATUS_KEY_PREFIX + uid; // 使用 uid 构建 Redis map 键
        RMap<String, ImUserClientInfo> statusMap = redissonClient.getMap(mapKey);

        // 构建客户端类型和设备号的键
        String clientKey = deviceType + "-" + imei;

        ImUserClientInfo imUserClientInfo = statusMap.get(clientKey);
        if(imUserClientInfo == null){
            return false;
        }

        return imUserClientInfo.getOnline().equals(OnlineStatusEnum.ONLINE.getCode());
    }

    private ImUserClientInfo userConnectConvert2Client(ImUserConnectInfo userConnectInfo, ChannelHandlerContext ctx, OnlineStatusEnum onlineStatusEnum, String ip) {
        ImUserClientInfo clientInfo = new ImUserClientInfo();
        clientInfo.setUserId(userConnectInfo.getUserId());
        clientInfo.setUserTyp(userConnectInfo.getUserType());
        clientInfo.setDeviceType(userConnectInfo.getDeviceType());
        clientInfo.setImei(userConnectInfo.getImei());
        clientInfo.setOnline(onlineStatusEnum.getCode());
        clientInfo.setLastLoginIp(ip);
        clientInfo.setLastLoginTime(DateUtil.now());
        return clientInfo;
    }

    @Override
    public void run(String... args) throws Exception {
        resetAllToOffline();
    }

    private void resetAllToOffline() {
        // 1. 使用Redisson的KeyScanner异步遍历（避免阻塞）
        RKeys keys = redissonClient.getKeys();
        String pattern = ONLINE_STATUS_KEY_PREFIX + "*"; // 如 "im:online:*"

        // 2. 分批扫描（每批1000个Key）
        int batchSize = 1000;
        Iterable<String> keysByPattern = keys.getKeysByPattern(pattern, batchSize);

        // 3. 处理扫描结果
        keysByPattern.forEach(clientKey -> {
            RMap<String, ImUserClientInfo> userMap = redissonClient.getMap(clientKey);
            updateUserMapToOffline(userMap); // 更新该用户的所有设备状态
        });
    }

    private void updateUserMapToOffline(RMap<String, ImUserClientInfo> userMap) {

        String now = DateUtil.formatDateTime(new Date());
        userMap.forEach((key, imUserClientInfo) -> {
            if (imUserClientInfo != null) {
                imUserClientInfo.setOnline(OnlineStatusEnum.OFFLINE.getCode());
                imUserClientInfo.setLastOfflineTime(now);
                userMap.put(key, imUserClientInfo);
            }
        });
    }
}
